﻿namespace Microsoft.SharePoint.Client
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Windows;

    public abstract class ClientRuntimeContext : IDisposable
    {
        private string m_applicationName = "Silverlight Library";
        private bool m_bProcessingResponse;
        private Dictionary<long, ObjectPath> m_objectPaths = new Dictionary<long, ObjectPath>();
        private ClientQueryProvider m_queryProvider;
        private ClientRequest m_request;
        private int m_requestTimeout = 0x2bf20;
        private Version m_serverLibraryVersion;
        private Version m_serverSchemaVersion;
        private Dictionary<string, object> m_staticObjects;
        private string m_url;
        private bool m_validateOnClient = true;
        internal static Version[] s_supportedSchemaVersions = new Version[] { ClientSchemaVersions.Version14 };

        public event EventHandler<WebRequestEventArgs> ExecutingWebRequest;

        protected ClientRuntimeContext(string webFullUrl)
        {
            if (webFullUrl == null)
            {
                throw new ArgumentNullException("webFullUrl");
            }
            if (!webFullUrl.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !webFullUrl.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase))
            {
                throw new ArgumentException();
            }
            this.m_url = webFullUrl;
        }

        public static void AddClientTypeAssembly(Assembly assembly)
        {
            SendOrPostCallback d = null;
            if (assembly == null)
            {
                throw new ArgumentNullException("assembly");
            }
            if (!Deployment.Current.Dispatcher.CheckAccess())
            {
                if (d == null)
                {
                    d = delegate {
                        try
                        {
                            ScriptTypeMap.EnsureInited();
                            ScriptTypeMap.AddClientProxyAssembly(assembly);
                        }
                        catch (Exception)
                        {
                        }
                    };
                }
                UIThreadSynchronizationContextInternal.Current.Send(d, null);
            }
            else
            {
                ScriptTypeMap.EnsureInited();
                ScriptTypeMap.AddClientProxyAssembly(assembly);
            }
        }

        internal void AddObjectPath(ObjectPath path)
        {
            this.m_objectPaths[path.Id] = path;
        }

        internal void AddObjectToQueryCleanupList(ClientObject obj)
        {
            this.PendingRequest.AddObjectToQueryCleanupList(obj);
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public void AddQuery(ClientAction query)
        {
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }
            this.PendingRequest.AddQuery(query);
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public void AddQueryIdAndResultObject(long id, object obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }
            this.PendingRequest.AddQueryIdAndResultObject(id, obj);
        }

        internal static bool CanHandleResponseSchema(Version responseSchemaVersion)
        {
            for (int i = 0; i < s_supportedSchemaVersions.Length; i++)
            {
                if (s_supportedSchemaVersions[i] == responseSchemaVersion)
                {
                    return true;
                }
            }
            return false;
        }

        public T CastTo<T>(ClientObject obj) where T: ClientObject
        {
            T local;
            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }
            Type c = typeof(T);
            if (!typeof(ClientObject).IsAssignableFrom(c))
            {
                throw new ArgumentException();
            }
            if (obj.Context != this)
            {
                throw new InvalidOperationException();
            }
            if (c.IsAssignableFrom(obj.GetType()))
            {
                local = (T) Activator.CreateInstance(c, new object[] { this, obj.Path });
                local.SetObjectDataFrom(obj);
                return local;
            }
            if ((obj.ObjectData.AssociatedObject != null) && c.IsAssignableFrom(obj.ObjectData.AssociatedObject.GetType()))
            {
                local = (T) Activator.CreateInstance(c, new object[] { this, obj.Path });
                local.SetObjectDataFrom(obj);
                return local;
            }
            if (!obj.GetType().IsAssignableFrom(c))
            {
                throw new ArgumentException();
            }
            if ((obj.ObjectData.AssociatedObject != null) && !obj.ObjectData.AssociatedObject.GetType().IsAssignableFrom(c))
            {
                throw new ArgumentException();
            }
            local = (T) Activator.CreateInstance(c, new object[] { this, obj.Path });
            local.SetObjectDataFrom(obj);
            object objB = null;
            if (obj.ObjectData.AssociatedObject == null)
            {
                objB = obj;
            }
            else
            {
                objB = obj.ObjectData.AssociatedObject;
            }
            if (objB != null)
            {
                List<string> list = new List<string>();
                Dictionary<string, object> queryIdToObjectMap = this.PendingRequest.QueryIdToObjectMap;
                foreach (KeyValuePair<string, object> pair in queryIdToObjectMap)
                {
                    if (object.ReferenceEquals(pair.Value, objB))
                    {
                        list.Add(pair.Key);
                    }
                }
                foreach (string str in list)
                {
                    queryIdToObjectMap[str] = local;
                }
                obj.ObjectData.AssociatedObject = local;
            }
            return local;
        }

        public void Dispose()
        {
            this.Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
        }

        public virtual void ExecuteQuery()
        {
            if (Deployment.Current.Dispatcher.CheckAccess())
            {
                throw new InvalidOperationException(Resources.GetString("NonAllowedInUIThread"));
            }
            using (SynchronousExecutor executor = new SynchronousExecutor(this))
            {
                executor.Invoke();
            }
        }

        public virtual void ExecuteQueryAsync(ClientRequestSucceededEventHandler succeededCallback, ClientRequestFailedEventHandler failedCallback)
        {
            ClientRequest request = this.SetPendingRequestToBeExecuted();
            this.ExecuteQueryAsync(request, succeededCallback, failedCallback);
        }

        protected virtual void ExecuteQueryAsync(ClientRequest request, ClientRequestSucceededEventHandler succeededCallback, ClientRequestFailedEventHandler failedCallback)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            if (request.Context != this)
            {
                throw new InvalidOperationException();
            }
            if (!ScriptTypeMap.IsInited)
            {
                if (!Deployment.Current.Dispatcher.CheckAccess())
                {
                    UIThreadSynchronizationContextInternal.Current.Send(delegate {
                        try
                        {
                            ScriptTypeMap.EnsureInited();
                        }
                        catch (Exception)
                        {
                        }
                    }, null);
                }
                else
                {
                    ScriptTypeMap.EnsureInited();
                }
            }
            try
            {
                request.ExecuteQuery(succeededCallback, failedCallback);
            }
            catch (Exception exception)
            {
                if (failedCallback != null)
                {
                    failedCallback(this, new ClientRequestFailedEventArgs(request, exception));
                }
            }
        }

        internal void FireExecutingWebRequestEvent(WebRequestEventArgs args)
        {
            this.OnExecutingWebRequest(args);
        }

        public void Load<T>(T clientObject, params Expression<Func<T, object>>[] retrievals) where T: ClientObject
        {
            if (clientObject == null)
            {
                throw new ArgumentNullException("clientObject");
            }
            DataRetrieval.Load<T>(clientObject, retrievals);
        }

        public IEnumerable<T> LoadQuery<T>(ClientObjectCollection<T> clientObjects) where T: ClientObject
        {
            if (clientObjects == null)
            {
                throw new ArgumentNullException("clientObjects");
            }
            ClientQueryableResult<T> result = new ClientQueryableResult<T>();
            ClientQueryInternal query = new ClientQueryInternal(clientObjects, null, false, null);
            query.ChildItemQuery.SelectAllProperties();
            clientObjects.Context.AddQueryIdAndResultObject(query.Id, result);
            clientObjects.Context.AddQuery(query);
            return result;
        }

        public IEnumerable<T> LoadQuery<T>(IQueryable<T> clientObjects) where T: ClientObject
        {
            if (clientObjects == null)
            {
                throw new ArgumentNullException("clientObjects");
            }
            ClientObjectCollection<T> objects = clientObjects as ClientObjectCollection<T>;
            if (objects != null)
            {
                return this.LoadQuery<T>(objects);
            }
            ClientQueryable<T> clientQueryable = clientObjects as ClientQueryable<T>;
            if (clientQueryable == null)
            {
                throw new NotSupportedException();
            }
            return DataRetrieval.Retrieve<T>(clientQueryable);
        }

        protected virtual void OnExecutingWebRequest(WebRequestEventArgs args)
        {
            EventHandler<WebRequestEventArgs> executingWebRequest = this.ExecutingWebRequest;
            if (executingWebRequest != null)
            {
                executingWebRequest(this, args);
            }
        }

        public object ParseObjectFromJsonString(string json)
        {
            if (string.IsNullOrEmpty(json))
            {
                return null;
            }
            StringReader reader = new StringReader(json);
            JsonReader reader2 = new JsonReader(reader, this);
            object obj2 = reader2.ReadObject();
            reader2.Dispose();
            reader.Dispose();
            return obj2;
        }

        protected ClientRequest SetPendingRequestToBeExecuted()
        {
            ClientRequest pendingRequest = this.PendingRequest;
            this.m_request = null;
            return pendingRequest;
        }

        public string ApplicationName
        {
            get
            {
                return this.m_applicationName;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                if (value.Length > 0x80)
                {
                    throw new ArgumentOutOfRangeException("value");
                }
                this.m_applicationName = value;
            }
        }

        public bool HasPendingRequest
        {
            get
            {
                return ((this.m_request != null) && (this.m_request.LastAction != null));
            }
        }

        internal Dictionary<long, ObjectPath> ObjectPaths
        {
            get
            {
                return this.m_objectPaths;
            }
        }

        public ClientRequest PendingRequest
        {
            get
            {
                if (this.m_request == null)
                {
                    this.m_request = new ClientRequest(this);
                }
                return this.m_request;
            }
        }

        internal bool ProcessingResponse
        {
            get
            {
                return this.m_bProcessingResponse;
            }
            set
            {
                this.m_bProcessingResponse = value;
            }
        }

        internal ClientQueryProvider QueryProvider
        {
            get
            {
                if (this.m_queryProvider == null)
                {
                    this.m_queryProvider = new ClientQueryProvider();
                }
                return this.m_queryProvider;
            }
        }

        public int RequestTimeout
        {
            get
            {
                return this.m_requestTimeout;
            }
            set
            {
                if ((value <= 0) && (value != -1))
                {
                    throw new ArgumentOutOfRangeException("value");
                }
                this.m_requestTimeout = value;
            }
        }

        public Version ServerLibraryVersion
        {
            get
            {
                if (this.m_serverLibraryVersion == null)
                {
                    throw new PropertyOrFieldNotInitializedException();
                }
                return this.m_serverLibraryVersion;
            }
            internal set
            {
                this.m_serverLibraryVersion = value;
            }
        }

        public Version ServerSchemaVersion
        {
            get
            {
                if (this.m_serverSchemaVersion == null)
                {
                    throw new PropertyOrFieldNotInitializedException();
                }
                return this.m_serverSchemaVersion;
            }
            internal set
            {
                this.m_serverSchemaVersion = value;
            }
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public Dictionary<string, object> StaticObjects
        {
            get
            {
                if (this.m_staticObjects == null)
                {
                    this.m_staticObjects = new Dictionary<string, object>();
                }
                return this.m_staticObjects;
            }
        }

        public object Tag { get; set; }

        public string Url
        {
            get
            {
                return this.m_url;
            }
        }

        public bool ValidateOnClient
        {
            get
            {
                return this.m_validateOnClient;
            }
            set
            {
                this.m_validateOnClient = value;
            }
        }

        private class SynchronousExecutor : IDisposable
        {
            private ClientRuntimeContext m_context;
            private ManualResetEvent m_event = new ManualResetEvent(false);
            private ClientRequestFailedEventArgs m_failedEventArgs;

            public SynchronousExecutor(ClientRuntimeContext context)
            {
                this.m_context = context;
            }

            public void Dispose()
            {
                if (this.m_event != null)
                {
                    this.m_event.Dispose();
                }
            }

            private void FailedCallback(object sender, ClientRequestFailedEventArgs args)
            {
                this.m_failedEventArgs = args;
                this.m_event.Set();
            }

            public void Invoke()
            {
                this.m_context.ExecuteQueryAsync(new ClientRequestSucceededEventHandler(this.SucceededCallback), new ClientRequestFailedEventHandler(this.FailedCallback));
                this.m_event.WaitOne(this.m_context.RequestTimeout);
                if (this.m_failedEventArgs != null)
                {
                    if (this.m_failedEventArgs.Exception != null)
                    {
                        throw this.m_failedEventArgs.Exception;
                    }
                    throw new ClientRequestException(this.m_failedEventArgs.Message);
                }
            }

            private void SucceededCallback(object sender, ClientRequestSucceededEventArgs args)
            {
                this.m_event.Set();
            }
        }
    }
}

